﻿/******************************************************************************
Module:  AsyncResult.cs
Notices: Copyright (c) 2006-2007 by Jeffrey Richter and Wintellect
******************************************************************************/


using System;
using System.IO;
using System.Threading;
using System.Reflection;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;


///////////////////////////////////////////////////////////////////////////////


namespace Microsoft.OfficeCommunicationsServer.Applications.Threading.Threading.AsyncProgModel {
   [DebuggerStepThrough]
   public class AsyncResult : IAsyncResult {
      // Fields set at construction which never change while operation is pending
      private readonly AsyncCallback m_AsyncCallback;
      private readonly Object m_AsyncState;
      private readonly Object m_InitiatingObject = null;

      // Field set at construction which do change after operation completes
      private const Int32 c_StatePending = 0;
      private const Int32 c_StateCompletedSynchronously = 1;
      private const Int32 c_StateCompletedAsynchronously = 2;
      private Int32 m_CompletedState = c_StatePending;

      // Field that may or may not get set depending on usage
      private ManualResetEvent m_AsyncWaitHandle;

      // Fields set when operation completes
      private Exception m_exception;

      // Find method to retain Exception's stack trace wehn caught and rethrown
      // NOTE: GetMethod returns null if method is not available
      private static MethodInfo s_Exception_InternalPreserveStackTrace =
         typeof(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic);

      public AsyncResult(AsyncCallback asyncCallback, Object state) {
         m_AsyncCallback = asyncCallback;
         m_AsyncState = state;
      }

      public AsyncResult(AsyncCallback asyncCallback, Object state, Object initiatingObject)
         : this(asyncCallback, state) {
         m_InitiatingObject = initiatingObject;
      }

      public Object InitiatingObject { get { return m_InitiatingObject; } }

      private static Exception PreserveExceptionStackTrace(Exception exception) {
         if (exception == null) return null;

         // Try the fast/hacky way first: Call Exception's non-public InternalPreserveStackTrace method to do it
         if (s_Exception_InternalPreserveStackTrace != null) {
            try {
               s_Exception_InternalPreserveStackTrace.Invoke(exception, null);
               return exception;
            }
            catch (MethodAccessException) {
               // Method can't be accessed, try serializing/deserializing the exception
            }
         }

         // The hacky way failed: Serialize and deserialize the exception object
         using (MemoryStream ms = new MemoryStream(1000)) {
            // Using CrossAppDomain causes the Exception to retain its stack
            BinaryFormatter formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.CrossAppDomain));
            formatter.Serialize(ms, exception);

            ms.Seek(0, SeekOrigin.Begin);
            return (Exception)formatter.Deserialize(ms);
         }
      }

      public void SetAsCompleted(Exception exception, Boolean completedSynchronously) {
         // Passing null for exception means no error occurred; this is the common case
         m_exception = PreserveExceptionStackTrace(exception);

         // The m_CompletedState field MUST be set prior calling the callback
         Int32 prevState = Interlocked.Exchange(ref m_CompletedState,
            completedSynchronously ? c_StateCompletedSynchronously : c_StateCompletedAsynchronously);
         if (prevState != c_StatePending)
            throw new InvalidOperationException("You can set a result only once");

         // If the event exists, set it
         if (m_AsyncWaitHandle != null) m_AsyncWaitHandle.Set();

         // If a callback method was set, call it
         if (m_AsyncCallback != null) m_AsyncCallback(this);
      }

      public void EndInvoke() {
         // This method assumes that only 1 thread calls EndInvoke for this object
         if (!IsCompleted) {
            // If the operation isn't done, wait for it
            AsyncWaitHandle.WaitOne();
            AsyncWaitHandle.Close();
            m_AsyncWaitHandle = null;  // Allow early GC
         }

         // Operation is done: if an exception occured, throw it
         if (m_exception != null) throw m_exception;
      }

      #region Implementation of IAsyncResult
      public Object AsyncState { get { return m_AsyncState; } }

      public Boolean CompletedSynchronously {
         get { return Thread.VolatileRead(ref m_CompletedState) == c_StateCompletedSynchronously; }
      }

      public WaitHandle AsyncWaitHandle {
         get {
            if (m_AsyncWaitHandle == null) {
               Boolean done = IsCompleted;
               ManualResetEvent mre = new ManualResetEvent(done);
               if (Interlocked.CompareExchange(ref m_AsyncWaitHandle, mre, null) != null) {
                  // Another thread created this object's event; dispose the event we just created
                  mre.Close();
               } else {
                  if (!done && IsCompleted) {
                     // If the operation wasn't done when we created 
                     // the event but now it is done, set the event
                     m_AsyncWaitHandle.Set();
                  }
               }
            }
            return m_AsyncWaitHandle;
         }
      }

      public Boolean IsCompleted {
         get { return Thread.VolatileRead(ref m_CompletedState) != c_StatePending; }
      }
      #endregion


      #region Helper Members
      private static AsyncCallback s_AsyncCallbackHelper = AsyncCallbackCompleteOpHelperNoReturnValue;
      protected static AsyncCallback GetAsyncCallbackHelper() { return s_AsyncCallbackHelper; }

      private static WaitCallback s_WaitCallbackHelper = WaitCallbackCompleteOpHelperNoReturnValue;
      protected IAsyncResult BeginInvokeOnWorkerThread() {
         ThreadPool.QueueUserWorkItem(s_WaitCallbackHelper, this);
         return this;
      }

      // This static method allows us to have just one static delegate 
      // instead of constructing a delegate per instance of this class
      private static void AsyncCallbackCompleteOpHelperNoReturnValue(IAsyncResult otherAsyncResult) {
         AsyncResult ar = (AsyncResult)otherAsyncResult.AsyncState;
         ar.CompleteOpHelper(otherAsyncResult);
      }

      private static void WaitCallbackCompleteOpHelperNoReturnValue(Object o) {
         AsyncResult ar = (AsyncResult)o;
         ar.CompleteOpHelper(null);
      }

      [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
         Justification = "OK because exception will be thrown by EndInvoke.")]
      private void CompleteOpHelper(IAsyncResult ar) {
         Exception exception = null;
         try {
            OnCompleteOperation(ar);
         }
         catch (TargetInvocationException e) {
            exception = e.InnerException;
         }
         catch (Exception e) {
            exception = e;
         }
         finally {
            SetAsCompleted(exception, false);
         }
      }

      protected virtual void OnCompleteOperation(IAsyncResult ar) { }
      #endregion
   }
}


///////////////////////////////////////////////////////////////////////////////


namespace Microsoft.OfficeCommunicationsServer.Applications.Threading.Threading.AsyncProgModel {
   [DebuggerStepThrough]
   public class AsyncResult<TResult>: AsyncResult {
      // Field set when operation completes
      private TResult m_result;

      public AsyncResult(AsyncCallback asyncCallback, Object state)
         : base(asyncCallback, state) {
      }

      public AsyncResult(AsyncCallback asyncCallback, Object state, Object initiatingObject)
         : base(asyncCallback, state, initiatingObject) {
      }

      public void SetAsCompleted(TResult result, Boolean completedSynchronously) {
         m_result = result;
         base.SetAsCompleted(null, completedSynchronously);
      }

      public new TResult EndInvoke() {
         base.EndInvoke(); // Wait until operation has completed 
         return m_result;  // Return the result (if above didn't throw)
      }

      #region Helper Members
      private static AsyncCallback s_AsyncCallbackHelper = AsyncCallbackCompleteOpHelperWithReturnValue;

      [SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes",
         Justification = "OK since member is protected")]
      protected new static AsyncCallback GetAsyncCallbackHelper() { return s_AsyncCallbackHelper; }

      private static void AsyncCallbackCompleteOpHelperWithReturnValue(IAsyncResult otherAsyncResult) {
         AsyncResult<TResult> ar = (AsyncResult<TResult>) otherAsyncResult.AsyncState;
         ar.CompleteOpHelper(otherAsyncResult);
      }

      private static WaitCallback s_WaitCallbackHelper = WaitCallbackCompleteOpHelperWithReturnValue;
      protected new IAsyncResult BeginInvokeOnWorkerThread() {
         ThreadPool.QueueUserWorkItem(s_WaitCallbackHelper, this);
         return this;
      }
      private static void WaitCallbackCompleteOpHelperWithReturnValue(Object o) {
         AsyncResult<TResult> ar = (AsyncResult<TResult>) o;
         ar.CompleteOpHelper(null);
      }

      [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
         Justification = "OK because exception will be thrown by EndInvoke.")]
      private void CompleteOpHelper(IAsyncResult ar) {
         try {
            SetAsCompleted(OnCompleteOperation(ar), false);
         }
         catch (TargetInvocationException e) {
            SetAsCompleted(e.InnerException, false);
         }
         catch (Exception e) {
            SetAsCompleted(e, false);
         }
      }
      protected new virtual TResult OnCompleteOperation(IAsyncResult ar) {
         return default(TResult);
      }
      #endregion
   }
}


///////////////////////////////////////////////////////////////////////////////


namespace Microsoft.OfficeCommunicationsServer.Applications.Threading.Threading.AsyncProgModel {
   [DebuggerStepThrough]
   public class AsyncResultReflection<TResult> : AsyncResult<TResult> {
      private readonly MethodInfo m_mi;
      private readonly Object m_target;
      private readonly Object[] m_args;

      public AsyncResultReflection(AsyncCallback ac, Object state, Object target, MethodInfo mi, params Object[] args)
         : base(ac, state) {
         m_target = target;
         m_mi = mi;
         m_args = args;
         BeginInvokeOnWorkerThread();
      }

      protected override TResult OnCompleteOperation(IAsyncResult ar) {
         return (TResult)m_mi.Invoke(m_target, m_args);
      }
   }
}


//////////////////////////////// End of File //////////////////////////////////